#include "stdafx.h"
#include "Snowflake.h"

#include "Engine.h"
#include "Filesystem.h"

const uint snowflake_levels = 3;
const uint snowflake_tris = 1896;
const uint crystal_sides = 7;
const uint crystal_branches = 512;
const uint crystal_tris = (crystal_branches-1) * crystal_sides * 2;

Snowflake::Snowflake(RendererPtr renderer, VectorBatchPtr vector_batch, SpriteBatchPtr sprite_batch, BitmapFontPtr font)
: renderer(renderer), vector_batch(vector_batch), sprite_batch(sprite_batch), font(font), 
camera(Vector3(0.0f, 0.0f, -5.0f), Vector3(0.0f, 0.0f, 1.0f), Vector3(0.0f, 1.0f, 0.0f)), prng(339)
{
}

Snowflake::~Snowflake()
{
}

void Snowflake::init(boost::weak_ptr<Demo> owner)
{
	this->owner = owner;

	reflection = video_device->getTextureManager()->loadTextureCube("reflection", Filesystem::redirect("eucalyptus_grove.dds"));

	hex_batch_effect = video_device->getEffectManager()->load("HexBatch", Filesystem::redirect("HexBatch.fx"));

	// Snowflake
	snowflake_effect = video_device->getEffectManager()->load("Snowflake", Filesystem::redirect("Snowflake.fx"));
	snowflake_effect->setParamValue(snowflake_effect->getParamBySemantic("ENV_REFLECTION"), boost::shared_polymorphic_cast<BaseTexture>(reflection)); 
	snowflake_effect->getAutoParams()->addDynamic("light1_attenuation", &light1_att);
	snowflake_effect->getAutoParams()->addDynamic("light2_attenuation", &light2_att);
	
	snowflake_vb = video_device->getGpuBufferManager()->createVertexBuffer("snowflake_vb", 
		VertexPosNormal::getFormat(), snowflake_tris * 3, false);

	Vector3 p[] = {
		Vector3(1.0f, 1.0f, 1.0f),
		Vector3(-1.0f, -1.0f, 1.0f),
		Vector3(-1.0f, 1.0f, -1.0f),
		Vector3(1.0f, -1.0f, -1.0f)
	};
	Vector3 p1[] = {p[0], p[2], p[1]};
	Vector3 p2[] = {p[0], p[3], p[2]};
	Vector3 p3[] = {p[0], p[1], p[3]};
	Vector3 p4[] = {p[1], p[2], p[3]};

	LockedBuffer lb = snowflake_vb->lock(false);
	VertexPosNormal* data = reinterpret_cast<VertexPosNormal*>(lb.data);
	genSnowflake(data, p1, snowflake_levels);
	genSnowflake(data, p2, snowflake_levels);
	genSnowflake(data, p3, snowflake_levels);
	genSnowflake(data, p4, snowflake_levels);
	snowflake_vb->unlock();

	MeshPtr snowflake_mesh(new Mesh(snowflake_vb, IndexBufferPtr()));
	snowflake = RenderableMeshPtr(new RenderableMesh(snowflake_mesh, snowflake_effect, EffectParamsPtr(), 100));

	// Crystal
	crystal_effect = video_device->getEffectManager()->load("Crystal", Filesystem::redirect("Crystal.fx"));
	crystal_effect->getAutoParams()->addDynamic("light1_attenuation", &light1_att);
	crystal_effect->getAutoParams()->addDynamic("light2_attenuation", &light2_att);

	crystal_vb = video_device->getGpuBufferManager()->createVertexBuffer("crystal_vb",
		VertexPosNormal::getFormat(), crystal_tris, false);
	lb = crystal_vb->lock(false);
	data = reinterpret_cast<VertexPosNormal*>(lb.data);
#ifndef _DEBUG
	genCrystal(data, crystal_branches);
#endif
	crystal_vb->unlock();

	crystal_ib = video_device->getGpuBufferManager()->createIndexBuffer("crystal_ib", true, crystal_tris * 3, false);
	lb = crystal_ib->lock(false);
	uint16* idata = reinterpret_cast<uint16*>(lb.data);
	genCrystalIB(idata, crystal_branches);
	crystal_ib->unlock();

	MeshPtr crystal_mesh(new Mesh(crystal_vb, crystal_ib));
	crystal = RenderableMeshPtr(new RenderableMesh(crystal_mesh, crystal_effect, EffectParamsPtr(), 120));

	// Hexes
	genHexes(128);
}

void Snowflake::close()
{
}

float beat2_factor(float t) {
	while(t >= 0.0f) {
		t -= 0.535714f;
	}
	return t*t;
}

void Snowflake::update(float t)
{
	float bf = beat2_factor(t);
	float t1 = transformT(44.0f, 48.0f, t);
	Vector3 crystal_pos = Vector3(smoothstep(8.0f, -1.0f, t1) + cos(t/3.2f)/4.0f, 
		smoothstep(sin(t*2.0f)/3.0f, -0.5f, t1)+ cos(t/4.2f)/4.0f, 
		-1.0f + cos(t/4.0f)/4.0f);
	if(t < 64.0f)
		crystal->setPosition(crystal_pos);
	else
		crystal->setPosition(crystal_pos 
			+ lerp(Vector3(0.0f, 0.0f, 0.0f), Vector3(sin(t*8.0f)*10.0f, cos(t*9.0f)*10.0f, cos(t*10.0f)*10.0f), transformT(64.0f, 65.0f, t)));
	if(t < 55.5f)
	{
		light1_att = 20.0f;
		light2_att = 20.0f;
		crystal->setScale(Vector3(10.0f, 10.0f, smootherstep(0.1f, 3.0f, transformT(55.0f, 55.5f, t))));
	}
	else
	{
		crystal->setScale(Vector3(10.0f, 10.0f, smootherstep(3.0f, 10.0f, transformT(55.5f, 56.0f, t))));
		light1_att = smootherstep(10.0f, 0.05f, transformT(57.0f, 58.6f, t));
		light2_att = smootherstep(10.0f, 0.01f, transformT(55.8f, 58.0f, t));
	}
	float t2 = transformT(54.0f, 56.0f, t);
	crystal->setRotation(Vector3(smoothstep(0.0f, cos(t/5.0f), t2), smoothstep(0.0f, sin(t/4.0f), t2), sin(t/4.5f)));

	snowflake->setRotation(Vector3(sin(t), cos(t), sin(t/2.0f)));
	
	float t3 = transformT(52.0f, 58.0f, t);
	float t4 = transformT(65.0f, 76.0f, t);
	Vector3 snowflake_jitter(sin(t*2.1f)/5.0f, cos(t*2.6f)/5.0f, cos(t*1.6f)/4.0f);
	if(t < 65.0f)
		snowflake->setPosition(Vector3(smootherstep(10.0f, 2.0f, t3), 1.2f, smootherstep(10.0f * bf / 10.0f, 1.7f, t3)) + snowflake_jitter);
	else
	{
		Vector3 pos = Vector3(smootherstep(2.0f, 0.6f, t4), smootherstep(1.2f, -0.1f, t4), smootherstep(1.7f, -1.0f, t4)) + snowflake_jitter;
		if(t < 75.0f)
			snowflake->setPosition(pos);
		else
			snowflake->setPosition(smoothstep(pos, Vector3(0.0f, 0.0f, 40.0f), transformT(75.0f, 78.0f, t)));
	}
	float s = smootherstep(0.0f, 1.0f, t3 - 0.1f);
	snowflake->setScale(Vector3(s, s, s));

	updateHexes(t);
}

void Snowflake::render(float t)
{
	static bool first = true;
	if(first) 
	{
		sprite_batch->setVirtualResolution(1024, 576);
		sprite_batch->setEffect(hex_batch_effect);
		first = false;
	}

	video_device->getRenderingParams().setCamera(camera);
	video_device->beginScene();
	
	// draw hexes
	drawHexes(t);
	
	renderer->prepare(*snowflake);
	if(t <= 65.0f)
		renderer->prepare(*crystal);
	renderer->render();
	video_device->endScene();
	renderer->endFrame();
}

void Snowflake::genSnowflake(VertexPosNormal*& out, Vector3 p[3], uint level) 
{
	Vector3 v1 = p[1] - p[0];
	Vector3 v2 = p[2] - p[0];
	Vector3 normal = v1.cross(v2).getNormalized();
	
	if(level == 0) 
	{
		*(out++) = VertexPosNormal(p[0], normal);
		*(out++) = VertexPosNormal(p[1], normal);
		*(out++) = VertexPosNormal(p[2], normal);
		return;
	}

	Vector3 m[3];
	m[0] = (p[0] + p[1]) / 2.0f;
	m[1] = (p[1] + p[2]) / 2.0f;
	m[2] = (p[2] + p[0]) / 2.0f;

	Vector3 c = (p[0] + p[1] + p[2]) / 3.0f;

	Vector3 nm[3];
	nm[0] = lerp(m[0], c, 0.25f);
	nm[1] = lerp(m[1], c, 0.25f);
	nm[2] = lerp(m[2], c, 0.25f);
	
	Vector3 d = nm[0] - nm[1];
	float b = d.length();
	float h = sqrtf(2.0f / 3.0f) * b;

	c += normal * h;

	Vector3 np1[] = {p[0], m[0], m[2]};
	Vector3 np2[] = {nm[2], nm[0], c};
	Vector3 np3[] = {p[1], m[1], m[0]};
	Vector3 np4[] = {nm[0], nm[1], c};
	Vector3 np5[] = {p[2], m[2], m[1]};
	Vector3 np6[] = {nm[1], nm[2], c};

	for(uint i = 0; i < 3; ++i) {
		*(out++) = VertexPosNormal(m[i], normal);
		*(out++) = VertexPosNormal(m[(i+1)%3], normal);
		*(out++) = VertexPosNormal(nm[(i+1)%3], normal);

		*(out++) = VertexPosNormal(nm[(i+1)%3], normal);
		*(out++) = VertexPosNormal(nm[i], normal);
		*(out++) = VertexPosNormal(m[i], normal);
	}

	genSnowflake(out, np1, level-1);
	genSnowflake(out, np2, level-1);
	genSnowflake(out, np3, level-1);
	genSnowflake(out, np4, level-1);
	genSnowflake(out, np5, level-1);
	genSnowflake(out, np6, level-1);
}

struct CrystalNode
{
	Vector3 pos;
	float radius;
	uint parent;
	uint childrens;
};

Vector3 Snowflake::random_spherepoint() 
{
	float z = prng.randFloat(-1.0f, 1.0f);
	float phi = prng.randFloat(0.0f, 2.0f * pi);
	float theta = acos(z);

	return Vector3(cos(phi) * cos(theta), cos(theta) * sin(phi), z);
}

void Snowflake::genCrystal(VertexPosNormal* out, uint branches) 
{
	std::vector<CrystalNode> nodes;
	CrystalNode center;
	center.pos = Vector3();
	center.radius = 0.1f;
	center.parent = 0;
	center.childrens = 0;
	nodes.push_back(center);

	// Generate crystal structure using diffusion-limited agregation
	while(nodes.size() < branches) 
	{
		Vector3 start = random_spherepoint();
		do 
		{
			Vector3 dir = random_spherepoint();
			start += dir * 0.04f;

			for(uint i = 0; i < nodes.size(); ++i)
			{
				Vector3 d = nodes[i].pos - start;
				if(d.lengthSq() < (nodes[i].radius + nodes[i].radius * 0.7) * (nodes[i].radius + nodes[i].radius * 0.7)) {
					CrystalNode new_node;
					new_node.pos = start;
					new_node.radius = nodes[i].radius * 0.7f;
					new_node.parent = i;
					new_node.childrens = 0;
					nodes[i].childrens++;
					nodes.push_back(new_node);
					
					start.x = 100.0f;
					break;
				}
			}
		} while(start.lengthSq() <= 1.0f);
	}

	// Generate geometry from structure
	uint write_idx = 0;
	for(uint i = 1; i < nodes.size(); ++i)
	{
		if(nodes[i].childrens == 0)
			nodes[i].radius = 0.0f;

		Vector3 dir = nodes[i].pos - nodes[nodes[i].parent].pos;
		Vector3 u(-dir.y, dir.x, 0.0f);
		u.normalize();
		Vector3 v = dir.cross(u).getNormalized();

		dir.normalize();

		for(uint j = 0; j < crystal_sides; ++j) 
		{
			float a = float(j) / float(crystal_sides) * pi * 2.0f;
			Vector3 n = sin(a) * u + cos(a) * v;
			Vector3 p1 = nodes[i].pos + n * nodes[i].radius / 10.0f;
			Vector3 p2;
			if(nodes[i].parent == 0)
			{
				p2 = nodes[0].pos + n * nodes[nodes[i].parent].radius / 10.0f;
			}
			else 
			{
				assert(i > nodes[i].parent);
				p2 = out[(nodes[i].parent - 1) * crystal_sides * 2 + j * 2].pos;
			}
			out[write_idx++] = VertexPosNormal(p1, n);
			out[write_idx++] = VertexPosNormal(p2, n);	
		}
	}
}

void Snowflake::genCrystalIB(uint16* out, uint branches)
{
	uint vtx_per_branch = crystal_sides * 2;
	uint idxs_per_branch = crystal_sides * 2 * 3;
	for(uint i = 0; i < branches-1; ++i)
	{
		for(uint j = 0; j < crystal_sides; ++j)
		{
			out[idxs_per_branch * i + j * 6 + 0] = uint16(vtx_per_branch * i + j * 2 + 1);
			out[idxs_per_branch * i + j * 6 + 1] = uint16(vtx_per_branch * i + j * 2);
			out[idxs_per_branch * i + j * 6 + 2] = uint16(vtx_per_branch * i + ((j+1)%crystal_sides) * 2 + 1);

			out[idxs_per_branch * i + j * 6 + 3] = uint16(vtx_per_branch * i + ((j+1)%crystal_sides) * 2);
			out[idxs_per_branch * i + j * 6 + 4] = uint16(vtx_per_branch * i + ((j+1)%crystal_sides) * 2 + 1);
			out[idxs_per_branch * i + j * 6 + 5] = uint16(vtx_per_branch * i + j * 2);
		}
	}
}

void Snowflake::genHexes(uint count)
{
	hexes.reserve(count);
	while(count--)
	{
		Hex h;
		h.pos = Vector2(prng.randFloat(0.0f, 1024.0f), prng.randFloat(0.0f, 576.0f));
		h.vel = Vector2(-1.0f, prng.randFloat(-1.5f, 1.5f));
		h.vel.normalize();
		h.vel *= 10.0f;
		h.rot = prng.randFloat(0.0f, pi * 2.0f);
		h.a_vel = prng.normal(0.0f, 0.01f);
		h.size = prng.normal(200.0f, 5000.0f);
		h.t = prng.normal(1.5f, 0.2f);
		h.c = Color(prng.randInt(70, 120), prng.randInt(8, 20), prng.randInt(15, 32));
		hexes.push_back(h);
	}
}

void Snowflake::updateHexes(float t)
{
	static float last_t = -1.0f;
	if(last_t == -1.0f)
	{
		last_t = t;
		return;
	}

	float dt = t - last_t;
	dt *= 10.0f;
	for(uint i = 0; i < hexes.size(); ++i)
	{
		Hex& h = hexes[i];
		h.pos += h.vel * dt;
		if(h.pos.x < -h.size / 2.0f)
			h.pos.x += 1024.0f + h.size;
		if(h.pos.y < -h.size / 2.0f)
			h.pos.y += 576.0f + h.size;
		if(h.pos.y > 576.0f + h.size / 2.0f)
			h.pos.y -= 576.0f + h.size;

		h.rot += h.a_vel * dt;
		if(h.rot > pi * 2.0f)
			h.rot -= pi * 2.0f;
		if(h.rot < 0.0f)
			h.rot += pi * 2.0f;
	}
	last_t = t;
}

void Snowflake::drawHexes(float t)
{

	const Vector2 center(1024.0f/2.0f, 576.0f/2.0f);
	const float fadein_length = 0.4f;
	float bf = beat2_factor(t);
	float gt = transformT(75.0f, 76.0f, t);
	gt = min(max(0.0f, gt), 1.0f);

	for(uint i = 0; i < hexes.size(); ++i)
	{
		bool brot = false;
		if(i % 3 == 0)
			brot = true;

		Hex& h = hexes[i];

		float cf = transformT(44.0f + h.t, 44.0f + h.t + fadein_length, t);
		Color transp = h.c;
		transp.a = 0;
		Color c = Color::smoothstep(transp, h.c, cf);

		Vector2 away = h.pos - center;
		away.normalize();
		away = center + away * 800.0f;

		Vector2 pos = lerp(h.pos, away, gt*gt);
		sprite_batch->draw(Texture2DPtr(), 0, RectF(0.0f, 0.0f, 1.0f, 1.0f),
			RectF(pos.x - h.size/2.0f, pos.y - h.size/2.0f, h.size, h.size), c, h.rot + (brot ? bf : 0.0f));
		/*
		if(h.pos.x < h.size/2.0f)
			sprite_batch->draw(Texture2DPtr(), 0, RectF(0.0f, 0.0f, 1.0f, 1.0f),
				RectF(h.pos.x - h.size/2.0f + 1024.0f, h.pos.y - h.size/2.0f, h.size, h.size), c, h.rot);
		if(h.pos.x > 1024.0f - h.size/2.0f)
			sprite_batch->draw(Texture2DPtr(), 0, RectF(0.0f, 0.0f, 1.0f, 1.0f),
				RectF(h.pos.x - h.size/2.0f - 1024.0f, h.pos.y - h.size/2.0f, h.size, h.size), c, h.rot);
		if(h.pos.y < h.size/2.0f)
			sprite_batch->draw(Texture2DPtr(), 0, RectF(0.0f, 0.0f, 1.0f, 1.0f),
				RectF(h.pos.x - h.size/2.0f, h.pos.y - h.size/2.0f + 576.0f, h.size, h.size), c, h.rot);
		if(h.pos.y > 576.0f - h.size/2.0f)
			sprite_batch->draw(Texture2DPtr(), 0, RectF(0.0f, 0.0f, 1.0f, 1.0f),
				RectF(h.pos.x - h.size/2.0f, h.pos.y - h.size/2.0f - 576.0f, h.size, h.size), c, h.rot);
		*/
	}
	sprite_batch->present();
}
